home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / Pascal / Libraries / WASTE 1.1a4 / WASTE Source / WESelecting.p < prev   
Encoding:
Text File  |  1994-11-10  |  43.4 KB  |  1,592 lines  |  [TEXT/PJMM]

  1. unit WESelecting;
  2.  
  3. { WASTE PROJECT }
  4. { Drawing Selections, Activating, Updating, Scrolling, etc. }
  5.  
  6. { Copyright © 1993-1994 Marco Piovanelli }
  7. { All Rights Reserved }
  8.  
  9. interface
  10.     uses
  11.         WELineLayout;
  12.  
  13.     function WEGetOffset (thePoint: LongPoint;
  14.                                     var edge: SignedByte;
  15.                                     hWE: WEHandle): LongInt;
  16.     procedure WEGetPoint (offset: LongInt;
  17.                                     var thePoint: LongPoint;
  18.                                     var lineHeight: Integer;
  19.                                     hWE: WEHandle);
  20.     function WEGetHiliteRgn (rangeStart, rangeEnd: LongInt;
  21.                                     hWE: WEHandle): RgnHandle;
  22.     procedure _WEDrawCaret (offset: LongInt;
  23.                                     hWE: WEHandle);
  24.     procedure _WEBlinkCaret (hWE: WEHandle);
  25.     procedure _WEHiliteRange (rangeStart, rangeEnd: LongInt;
  26.                                     hWE: WEHandle);
  27.     procedure WEUpdate (updateRgn: RgnHandle;
  28.                                     hWE: WEHandle);
  29.     procedure WEScroll (hOffset, vOffset: LongInt;
  30.                                     hWE: WEHandle);
  31.     function _WEScrollIntoView (offset: LongInt;
  32.                                     hWE: WEHandle): Boolean;
  33.     procedure WESelView (hWE: WEHandle);
  34.     procedure WEActivate (hWE: WEHandle);
  35.     procedure WEDeactivate (hWE: WEHandle);
  36.     function WEIsActive (hWE: WEHandle): Boolean;
  37.     procedure _WEDoArrowKey (arrow: Integer;
  38.                                     modifiers: Integer;
  39.                                     hWE: WEHandle);
  40.     function WEAdjustCursor (mouseLoc: Point;
  41.                                     mouseRgn: RgnHandle;
  42.                                     hWE: WEHandle): Boolean;
  43.     procedure WEIdle (var maxSleep: LongInt;
  44.                                     hWE: WEHandle);
  45.     procedure WESetSelection (selStart, selEnd: LongInt;
  46.                                     hWE: WEHandle);
  47.     procedure WESetAlignment (alignment: SignedByte;
  48.                                     hWE: WEHandle);
  49.     procedure WEFindWord (offset: LongInt;
  50.                                     edge: SignedByte;
  51.                                     var wordStart, wordEnd: LongInt;
  52.                                     hWE: WEHandle);
  53.     procedure WEFindLine (offset: LongInt;
  54.                                     edge: SignedByte;
  55.                                     var lineStart, lineEnd: LongInt;
  56.                                     hWE: WEHandle);
  57.     function WECharByte (offset: LongInt;
  58.                                     hWE: WEHandle): Integer;
  59.     function WECharType (offset: LongInt;
  60.                                     hWE: WEHandle): Integer;
  61.  
  62. implementation
  63.     uses
  64.         Drag, ExternQD, QDOffscreen, TextServices;
  65.  
  66. { If WASTE_OBJECTS_ARE_GLYPHS is TRUE, WEGetOffset treats embedded objects }
  67. { like ordinary glyphs and never returns kObjectEdge in the edge parameter: }
  68. { as a result, clicking in the middle of an object always positions the caret either }
  69. { to the left or to the right of the object. }
  70.  
  71. { If WASTE_OBJECTS_ARE_GLYPHS is FALSE, WEGetOffset returns kObjectEdge in }
  72. { the edge parameter when thePoint is in the middle half of an object: as a result, }
  73. { clicking in the middle of an object immediately _selects_ the object, so that a second }
  74. { click immediately following triggers the 'clik' callback (this can be handy to make }
  75. { sound objects play when they are double-clicked. }
  76.  
  77. {$SETC WASTE_OBJECTS_ARE_GLYPHS = TRUE}
  78.  
  79.     const
  80.  
  81. { values for _WEArrowOffset action parameter: }
  82. { plain arrow keys }
  83.         kGoLeft = 0;
  84.         kGoRight = 1;
  85.         kGoUp = 2;
  86.         kGoDown = 3;
  87.  
  88. { modifiers }
  89.         kOption = 4;
  90.         kCommand = 8;
  91.  
  92. { option + arrow combos }
  93.         kGoWordStart = kGoLeft + kOption;
  94.         kGoWordEnd = kGoRight + kOption;
  95.         kGoTextStart = kGoUp + kOption;
  96.         kGoTextEnd = kGoDown + kOption;
  97.  
  98. { command + arrow combos }
  99.         kGoLineStart = kGoLeft + kCommand;
  100.         kGoLineEnd = kGoRight + kCommand;
  101.         kGoPageStart = kGoUp + kCommand;
  102.         kGoPageEnd = kGoDown + kCommand;
  103.  
  104.     procedure ClearHiliteBit;
  105.     inline
  106.         $08B8, $0007, $0938;         { bclr #7, HiliteMode }
  107.  
  108.     function WEGetOffset (thePoint: LongPoint;
  109.                                     var edge: SignedByte;
  110.                                     hWE: WEHandle): LongInt;
  111.  
  112. { given a long point in local coordinates, }
  113. { find the text offset corresponding to the nearest glyph }
  114.  
  115.         var
  116.             pWE: WEPtr;
  117.             lineIndex: LongInt;
  118.             pixelWidth: Fixed;
  119.             saveWELock: Boolean;
  120.  
  121.         function SLPixelToChar (pLine: LinePtr;
  122.                                         pAttrs: WERunAttributesPtr;
  123.                                         pSegment: Ptr;
  124.                                         segmentStart, segmentLength: LongInt;
  125.                                         styleRunPosition: JustStyleCode): Boolean;
  126.             var
  127.                 slop, width, subWidth: Fixed;
  128.                 cType: Integer;
  129.         begin
  130.             SLPixelToChar := false;
  131.  
  132. { if this is the first style run on the line, subtract pen indent from pixelWidth }
  133.             if (styleRunPosition <= smLeftStyleRun) then
  134.                 pixelWidth := pixelWidth - BSL(_WECalcPenIndent(pLine^.lineSlop, pWE^.alignment), 16);
  135.  
  136. { if pixel width is gone negative already, the point is on the trailing edge of first glyph }
  137.             if (pixelWidth < 0) then
  138.                 begin
  139.                     WEGetOffset := segmentStart;
  140.                     edge := kTrailingEdge;
  141.                     SLPixelToChar := true;
  142.                     Exit(SLPixelToChar);
  143.                 end;
  144.  
  145.             if (pAttrs^.runStyle.tsObject <> kNullObject) then
  146.                 begin
  147.  
  148. { EMBEDDED OBJECT }
  149. { calculate object width as Fixed }
  150.                     width := BSL(WEObjectDescHandle(pAttrs^.runStyle.tsObject)^^.objectSize.h, 16);
  151.  
  152. { subtract object width from pixelWidth }
  153.                     pixelWidth := pixelWidth - width;
  154.  
  155. {$IFC WASTE_OBJECTS_ARE_GLYPHS}
  156.  
  157. { find out whether the point is in the leftmost half of the object, }
  158. { in the rightmost half or past the object }
  159.                     subWidth := BSR(width, 1);                    { divide by two }
  160.                     if (pixelWidth + subWidth < 0) then
  161.                         begin
  162.                             WEGetOffset := segmentStart;
  163.                             edge := kLeadingEdge;                    { point is in leftmost half of object }
  164.                         end
  165.                     else
  166.                         begin
  167.                             WEGetOffset := segmentStart + 1;
  168.                             if (pixelWidth < 0) then
  169.                                 edge := kTrailingEdge                    { point is in rightmost half of object }
  170.                             else
  171.                                 edge := kLeadingEdge;                    { point is past object }
  172.                         end;
  173.  
  174. {$ELSEC}
  175.  
  176. { find out whether the point is in the leftmost quarter of the object, }
  177. { in the middle half, in the rightmost quarter or past the object }
  178.                     subWidth := BSR(width, 2);                    { divide by four }
  179.                     if (pixelWidth + subWidth < 0) then
  180.                         begin
  181.                             WEGetOffset := segmentStart;
  182.                             if (pixelWidth + width < subWidth) then
  183.                                 edge := kLeadingEdge                    { point is in leftmost quarter of object }
  184.                             else
  185.                                 edge := kObjectEdge;                        { point is in middle half of object }
  186.                         end
  187.                     else
  188.                         begin
  189.                             WEGetOffset := segmentStart + 1;
  190.                             if (pixelWidth < 0) then
  191.                                 edge := kTrailingEdge                    { point is in rightmost quarter of object }
  192.                             else
  193.                                 edge := kLeadingEdge;                    { point is past object }
  194.                         end;
  195.  
  196. {$ENDC}
  197.  
  198.                 end
  199.             else
  200.                 begin
  201.  
  202. { REGULAR TEXT }
  203. { if this is the last segment on the line, strip the last blank character (if any), }
  204. { unless it is the last non-CR character in the whole text stream }
  205.                     if (not Odd(styleRunPosition)) then
  206.                         if (segmentStart + segmentLength < pWE^.textLength) | (Ptr(LongInt(pSegment) + segmentLength - 1)^ = kEOL) then
  207.                             begin
  208.                                 cType := CharType(pSegment, segmentLength - 1);
  209.                                 if (BitAnd(cType, smcTypeMask + smcClassMask) = smCharPunct + smPunctBlank) then
  210.                                     if (BitAnd(cType, smcDoubleMask) = 0) then
  211.                                         segmentLength := segmentLength - 1
  212.                                     else
  213.                                         segmentLength := segmentLength - 2;
  214.                             end;
  215.  
  216. { calculate slop for this text segment (justified text only) }
  217.                     if (pWE^.alignment = weJustify) then
  218.                         slop := FixMul(NPortionText(pSegment, segmentLength, styleRunPosition, Point(kOneToOneScaling), Point(kOneToOneScaling)), pLine^.lineJustAmount)
  219.                     else
  220.                         slop := 0;
  221.  
  222. { call PixelToChar for this segment }
  223.                     WEGetOffset := segmentStart + NPixel2Char(pSegment, segmentLength, slop, pixelWidth, Boolean(edge), width, styleRunPosition, Point(kOneToOneScaling), Point(kOneToOneScaling));
  224.  
  225. { update pixelWidth for next iteration }
  226.                     pixelWidth := width;
  227.  
  228.                 end;
  229.  
  230. { if pixelWidth has gone negative, we're finished; otherwise go to next run }
  231.             if (pixelWidth < 0) then
  232.                 SLPixelToChar := true;
  233.  
  234.         end;  { SLPixelToChar }
  235.  
  236.     begin
  237.  
  238. { lock the WE record }
  239.         saveWELock := _WESetHandleLock(hWE, true);
  240.         pWE := hWE^;
  241.  
  242. { offset thePoint so that it is relative to the top left corner of the destination rectangle }
  243.         thePoint.v := thePoint.v - pWE^.destRect.top;
  244.         thePoint.h := thePoint.h - pWE^.destRect.left;
  245.  
  246. { if the point is above the destination rect, return zero }
  247.         if (thePoint.v < 0) then
  248.             begin
  249.                 WEGetOffset := 0;
  250.                 edge := kTrailingEdge;
  251.             end
  252.         else
  253.  
  254. { if the point is below the last line, return last char offset }
  255.             if (thePoint.v >= WEGetHeight(0, maxLongInt, hWE)) then
  256.                 begin
  257.                     WEGetOffset := pWE^.textLength;
  258.                     edge := kLeadingEdge;
  259.                 end
  260.             else
  261.                 begin
  262.  
  263. { find the line index corresponding to the vertical pixel offset }
  264.                     lineIndex := _WEPixelToLine(thePoint.v, hWE);
  265.  
  266. { express the horizontal pixel offset as a Fixed value }
  267.                     pixelWidth := BSL(thePoint.h, 16);
  268.  
  269. { walk through the segments on this line calling PixelToChar }
  270.                     _WESegmentLoop(lineIndex, lineIndex, SLPixelToChar, hWE);
  271.  
  272.                 end;  { else }
  273.  
  274. { unlock the WE record }
  275.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  276.  
  277.     end;  { WEGetOffset }
  278.  
  279.     procedure WEGetPoint (offset: LongInt;
  280.                                     var thePoint: LongPoint;
  281.                                     var lineHeight: Integer;
  282.                                     hWE: WEHandle);
  283.  
  284. { given a byte offset into the text, find the corresponding glyph position }
  285. { this routine is useful for highlighting the text and for positioning the caret }
  286.  
  287.         var
  288.             pWE: WEPtr;
  289.             pLine: LinePeek;
  290.             lineIndex: LongInt;
  291.             saveWELock: Boolean;
  292.  
  293.         function SLCharToPixel (pLine: LinePtr;
  294.                                         pAttrs: WERunAttributesPtr;
  295.                                         pSegment: Ptr;
  296.                                         segmentStart, segmentLength: LongInt;
  297.                                         styleRunPosition: JustStyleCode): Boolean;
  298.             var
  299.                 slop: Fixed;
  300.                 width: Integer;
  301.                 isInSegment: Boolean;
  302.         begin
  303.  
  304. { is offset within this segment? }
  305.             isInSegment := (offset < segmentStart + segmentLength);
  306.  
  307. { if this is the first style run on the line, add pen indent to thePoint.h }
  308.             if (styleRunPosition <= smLeftStyleRun) then
  309.                 thePoint.h := thePoint.h + _WECalcPenIndent(pLine^.lineSlop, pWE^.alignment);
  310.  
  311.             if (pAttrs^.runStyle.tsObject <> kNullObject) then
  312.                 begin
  313.  
  314. { EMBEDDED OBJECT }
  315.                     if (isInSegment) then
  316.                         width := 0
  317.                     else
  318.                         width := WEObjectDescHandle(pAttrs^.runStyle.tsObject)^^.objectSize.h;
  319.  
  320.                 end
  321.             else
  322.                 begin
  323.  
  324. { REGULAR TEXT }
  325. { calculate slop for this text segment (justified text only) }
  326.                     if (pWE^.alignment = weJustify) then
  327.                         slop := FixMul(NPortionText(pSegment, segmentLength, styleRunPosition, Point(kOneToOneScaling), Point(kOneToOneScaling)), pLine^.lineJustAmount)
  328.                     else
  329.                         slop := 0;
  330.  
  331. { call CharToPixel to get width of segment up to specified offset }
  332.                     width := NChar2Pixel(pSegment, segmentLength, slop, offset - segmentStart, smHilite, styleRunPosition, Point(kOneToOneScaling), Point(kOneToOneScaling));
  333.  
  334.                 end;
  335.  
  336. { advance thePoint.h by the width of this segment }
  337.             thePoint.h := thePoint.h + width;
  338.  
  339. { drop out of loop when we reach offset }
  340.             SLCharToPixel := isInSegment;
  341.  
  342.         end;  { SLCharToPixel }
  343.  
  344.     begin
  345.  
  346. { lock the WE record }
  347.         saveWELock := _WESetHandleLock(hWE, true);
  348.         pWE := hWE^;
  349.  
  350. { the base point is the top left corner of the destination rectangle }
  351.         thePoint := pWE^.destRect.topLeft;
  352.  
  353. { first of all find the line on which the glyph lies }
  354.         lineIndex := WEOffsetToLine(offset, hWE);
  355.  
  356. { calculate the vertical coordinate and the line height }
  357.         pLine := @pWE^.hLines^^[lineIndex];
  358.         thePoint.v := thePoint.v + pLine^.first.lineOrigin;
  359.         lineHeight := pLine^.second.lineOrigin - pLine^.first.lineOrigin;
  360.  
  361.         if ((offset = pWE^.textLength) & (WEGetChar(offset - 1, hWE) = CHR(kEOL))) then
  362.             begin
  363.  
  364. { SPECIAL CASE: if offset is past the last character and }
  365. { the last character is a carriage return, return a point below the last line }
  366.  
  367.                 thePoint.v := thePoint.v + lineHeight;
  368.                 thePoint.h := thePoint.h + _WECalcPenIndent(pWE^.destRect.right - pWE^.destRect.left, pWE^.alignment);
  369.             end
  370.         else
  371.  
  372. { to get the horizontal coordinate, walk through the style runs on this line }
  373.             _WESegmentLoop(lineIndex, lineIndex, SLCharToPixel, hWE);
  374.  
  375. { pin the horizontal coordinate to the destination rectangle }
  376.         thePoint.h := _WEPinInRange(thePoint.h, pWE^.destRect.left, pWE^.destRect.right);
  377.  
  378. { unlock the WE record }
  379.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  380.  
  381.     end;  { WEGetPoint }
  382.  
  383.     procedure WEFindLine (offset: LongInt;
  384.                                     edge: SignedByte;
  385.                                     var lineStart, lineEnd: LongInt;
  386.                                     hWE: WEHandle);
  387.         var
  388.             pWE: WEPtr;
  389.             pLine: LineArrayPtr;
  390.     begin
  391.         pWE := hWE^;
  392.         pLine := @pWE^.hLines^^[WEOffsetToLine(offset, hWE)];
  393.         lineStart := pLine^[0].lineStart;
  394.         lineEnd := pLine^[1].lineStart;
  395.     end;  { WEFindLine }
  396.  
  397.     function _WEGetContext (offset: LongInt;
  398.                                     var contextStart, contextEnd: LongInt;
  399.                                     hWE: WEHandle): Integer;
  400.  
  401. { This function finds a range of characters ("context"), all belonging to the same script }
  402. { and centered around the specified offset. }
  403. { The function result is the ID of a font belonging to this script. }
  404. { Ideally, the context should consist of a whole script run, but in practice the returned }
  405. { context can be narrower, for performance and other reasons (see below) }
  406.  
  407.         var
  408.             index, saveIndex, saveRunEnd: LongInt;
  409.             runInfo: WERunInfo;
  410.             script1, script2: ScriptCode;
  411.     begin
  412.         if BTST(hWE^^.flags, weFNonRoman) then
  413.             begin
  414.  
  415. { if more than one script is installed, limit the search of script run boundaries }
  416. { to a single line, for speed's sake }
  417.                 WEFindLine(offset, kLeadingEdge, contextStart, contextEnd, hWE);
  418.  
  419. { find the style run the specified offset is in }
  420.                 index := _WEOffsetToRun(offset, hWE);
  421.                 _WEGetIndStyle(index, runInfo, hWE);
  422.  
  423. { return the style run font as function result }
  424.                 _WEGetContext := runInfo.runAttrs.runStyle.tsFont;
  425.  
  426. { find the script code associated with this style run }
  427.                 script1 := Font2Script(runInfo.runAttrs.runStyle.tsFont);
  428.  
  429. { save index and runInfo.runEnd for the second while loop }
  430.                 saveIndex := index;
  431.                 saveRunEnd := runInfo.runEnd;
  432.  
  433. { walk backwards across style runs preceding offset, looking for a script run boundary }
  434.                 while (runInfo.runStart > contextStart) do
  435.                     begin
  436.                         index := index - 1;
  437.                         _WEGetIndStyle(index, runInfo, hWE);
  438.                         script2 := Font2Script(runInfo.runAttrs.runStyle.tsFont);
  439.                         if (script1 <> script2) then
  440.                             begin
  441.                                 contextStart := runInfo.runEnd;
  442.                                 Leave;
  443.                             end;
  444.                     end;  { while }
  445.  
  446. { restore index and runInfo.runEnd }
  447.                 index := saveIndex;
  448.                 runInfo.runEnd := saveRunEnd;
  449.  
  450. { walk forward across style runs following offset, looking for a script run boundary }
  451.                 while (runInfo.runEnd < contextEnd) do
  452.                     begin
  453.                         index := index + 1;
  454.                         _WEGetIndStyle(index, runInfo, hWE);
  455.                         script2 := Font2Script(runInfo.runAttrs.runStyle.tsFont);
  456.                         if (script1 <> script2) then
  457.                             begin
  458.                                 contextEnd := runInfo.runStart;
  459.                                 Leave;
  460.                             end;
  461.                     end;  { while }
  462.  
  463.             end
  464.         else
  465.             begin
  466.  
  467. { only the Roman script is enabled: the whole text constitutes one script run }
  468.                 _WEGetContext := systemFont;
  469.                 contextStart := 0;
  470.                 contextEnd := hWE^^.textLength;
  471.             end;
  472.  
  473. { make sure the range identified by contextStart/contextEnd is contained within }
  474. { the 32K byte range centered around the specified offset }
  475. { the reason for this is that many Script Manager routines (e.g. FindWord and CharByte) }
  476. { only accept 16-bit offsets, rather than 32-bit offsets }
  477.  
  478.         contextStart := _WEPinInRange(contextStart, offset - (maxint div 2), offset);
  479.         contextEnd := _WEPinInRange(contextEnd, offset, offset + (maxint div 2));
  480.  
  481.     end;  { _WEGetContext }
  482.  
  483.     function _WEGetRestrictedContext (offset: LongInt;
  484.                                     var contextStart, contextEnd: LongInt;
  485.                                     hWE: WEHandle): Integer;
  486.  
  487. { This function finds a range of characters ("context"), all belonging to the same script }
  488. { and centered around the specified offset. }
  489. { This function returns a script run subrange and is more efficient than }
  490. { _WEGetContext because it doesn't try to find the script boundaries accurately. }
  491.  
  492.         var
  493.             runInfo: WERunInfo;
  494.     begin
  495.  
  496. { just find the style run the specified offset is in }
  497.         WEGetRunInfo(offset, runInfo, hWE);
  498.         contextStart := runInfo.runStart;
  499.         contextEnd := runInfo.runEnd;
  500.         _WEGetRestrictedContext := runInfo.runAttrs.runStyle.tsFont;
  501.  
  502. { make sure the range identified by contextStart/contextEnd is contained within }
  503. { the 32K byte range centered around the specified offset }
  504. { the reason for this is that many Script Manager routines (e.g. FindWord and CharByte) }
  505. { only accept 16-bit offsets, rather than 32-bit offsets }
  506.  
  507.         contextStart := _WEPinInRange(contextStart, offset - (maxint div 2), offset);
  508.         contextEnd := _WEPinInRange(contextEnd, offset, offset + (maxint div 2));
  509.  
  510.     end;  { _WEGetRestrictedContext }
  511.  
  512.     procedure WEFindWord (offset: LongInt;
  513.                                     edge: SignedByte;
  514.                                     var wordStart, wordEnd: LongInt;
  515.                                     hWE: WEHandle);
  516.         var
  517.             pWE: WEPtr;
  518.             contextStart, contextEnd: LongInt;
  519.             hText: Handle;
  520.             wordBreaks: OffsetTable;
  521.             saveFont: Integer;
  522.             savePort: GrafPtr;
  523.             saveTextLock: Boolean;
  524.     begin
  525.         pWE := hWE^;
  526.         hText := pWE^.hText;
  527.  
  528. { set up the port }
  529.         GetPort(savePort);
  530.         SetPort(pWE^.port);
  531.  
  532. { find a script context containing the specified offset }
  533. { (words cannot straddle script boundaries) }
  534. { and set the port font to the corresponding script font }
  535.  
  536.         saveFont := pWE^.port^.txFont;
  537.         TextFont(_WEGetContext(offset, contextStart, contextEnd, hWE));
  538.  
  539. { lock the text }
  540.         saveTextLock := _WESetHandleLock(hText, true);
  541.  
  542. { find word breaks }
  543.         FindWord(Ptr(LongInt(hText^) + contextStart), contextEnd - contextStart, offset - contextStart, Boolean(edge), nil, wordBreaks);
  544.  
  545. { unlock the text }
  546.         IgnoreBoolean(_WESetHandleLock(hText, saveTextLock));
  547.  
  548. { restore font and port }
  549.         TextFont(saveFont);
  550.         SetPort(savePort);
  551.  
  552. { calculate wordStart and wordEnd relative to the beginning of the text }
  553.         wordStart := contextStart + wordBreaks[0].offFirst;
  554.         wordEnd := contextStart + wordBreaks[0].offSecond;
  555.  
  556.     end;  { WEFindWord }
  557.  
  558.     function WECharByte (offset: LongInt;
  559.                                     hWE: WEHandle): Integer;
  560.         var
  561.             pWE: WEPtr;
  562.             contextStart, contextEnd: LongInt;
  563.             saveFont: Integer;
  564.             savePort: GrafPtr;
  565.     begin
  566.         WECharByte := smSingleByte;
  567.         pWE := hWE^;
  568.  
  569. { exit now if there is no double-byte script system installed }
  570.         if (not BTST(pWE^.flags, weFDoubleByte)) then
  571.             Exit(WECharByte);
  572.  
  573. { sanity check: make sure offset is within allowed bounds }
  574.         if ((offset < 0) or (offset >= pWE^.textLength)) then
  575.             Exit(WECharByte);
  576.  
  577. { save the port }
  578.         GetPort(savePort);
  579.         SetPort(pWE^.port);
  580.  
  581. { find a script context containing the specified offset }
  582. { and set the port font to the corresponding script font }
  583.         saveFont := pWE^.port^.txFont;
  584.         TextFont(_WEGetRestrictedContext(offset, contextStart, contextEnd, hWE));
  585.  
  586. { pass CharByte a pointer to the beginning of the style run }
  587. { CharByte is guaranteed not to move memory, so the text needn't be locked }
  588.         WECharByte := CharByte(Ptr(LongInt(pWE^.hText^) + contextStart), offset - contextStart);
  589.  
  590. { restore the port font }
  591.         TextFont(saveFont);
  592.  
  593. { restore the port }
  594.         SetPort(savePort);
  595.  
  596.     end;  { WECharByte }
  597.  
  598.     function WECharType (offset: LongInt;
  599.                                     hWE: WEHandle): Integer;
  600.         var
  601.             pWE: WEPtr;
  602.             contextStart, contextEnd: LongInt;
  603.             hText: Handle;
  604.             saveFont: Integer;
  605.             savePort: GrafPtr;
  606.             saveTextLock: Boolean;
  607.     begin
  608.         WECharType := 0;
  609.         pWE := hWE^;
  610.         hText := pWE^.hText;
  611.  
  612. { sanity check: make sure offset is within allowed bounds }
  613.         if ((offset < 0) or (offset >= pWE^.textLength)) then
  614.             Exit(WECharType);
  615.  
  616. { save the port }
  617.         GetPort(savePort);
  618.         SetPort(pWE^.port);
  619.  
  620. { find a script context containing the specified offset }
  621. { and set the port font to the corresponding script font }
  622.         saveFont := pWE^.port^.txFont;
  623.         TextFont(_WEGetRestrictedContext(offset, contextStart, contextEnd, hWE));
  624.  
  625. { lock the text (CharType may move memory) }
  626.         saveTextLock := _WESetHandleLock(hText, true);
  627.  
  628. { pass CharType a pointer to the beginning of the style run }
  629.         WECharType := CharType(Ptr(LongInt(hText^) + contextStart), offset - contextStart);
  630.  
  631. { unlock the text }
  632.         IgnoreBoolean(_WESetHandleLock(hText, saveTextLock));
  633.  
  634. { restore the port font }
  635.         TextFont(saveFont);
  636.  
  637. { restore the port }
  638.         SetPort(savePort);
  639.  
  640.     end;  { WECharType }
  641.  
  642.     procedure _WEDrawCaret (offset: LongInt;
  643.                                     hWE: WEHandle);
  644.         var
  645.             pWE: WEPtr;
  646.             thePoint: LongPoint;
  647.             caretRect: Rect;
  648.             caretHeight: Integer;
  649.             savePort: GrafPtr;
  650.             saveClip: RgnHandle;
  651.     begin
  652.  
  653. { the WE record must be already locked }
  654.         pWE := hWE^;
  655.  
  656. { find the caret position using WEGetPoint }
  657.         WEGetPoint(offset, thePoint, caretHeight, hWE);
  658.         WELongPointToPoint(thePoint, caretRect.topLeft);
  659.         if (caretRect.left > pWE^.destRect.left) then
  660.             caretRect.left := caretRect.left - 1;
  661.  
  662. { calculate caret rectangle }
  663.         caretRect.bottom := caretRect.top + caretHeight;
  664.         caretRect.right := caretRect.left + kCaretWidth;
  665.  
  666. { set up the port }
  667.         GetPort(savePort);
  668.         SetPort(pWE^.port);
  669.  
  670. { clip to the view region }
  671.         saveClip := NewRgn;
  672.         GetClip(saveClip);
  673.         SetClip(pWE^.viewRgn);
  674.  
  675. { draw the caret }
  676.         InvertRect(caretRect);
  677.  
  678. { restore the clip region }
  679.         SetClip(saveClip);
  680.         DisposeRgn(saveClip);
  681.  
  682. { restore the port }
  683.         SetPort(savePort);
  684.  
  685.     end;  { _WEDrawCaret }
  686.  
  687.     procedure _WEBlinkCaret (hWE: WEHandle);
  688.         var
  689.             pWE: WEPtr;
  690.     begin
  691.  
  692. { the WE record must be already locked }
  693.         pWE := hWE^;
  694.  
  695. { do nothing if we're not active }
  696.         if (not BTST(pWE^.flags, weFActive)) then
  697.             Exit(_WEBlinkCaret);
  698.  
  699. { redraw the caret, in XOR mode }
  700.         _WEDrawCaret(pWE^.selStart, hWE);
  701.  
  702. { keep track of the current caret visibility status }
  703.         pWE^.flags := BitXor(pWE^.flags, BSL(1, weFCaretVisible));
  704.  
  705. { update caretTime }
  706.         pWE^.caretTime := TickCount;
  707.  
  708.     end;  { _WEBlinkCaret }
  709.  
  710.     function WEGetHiliteRgn (rangeStart, rangeEnd: LongInt;
  711.                                     hWE: WEHandle): RgnHandle;
  712.  
  713. { returns the hilite region corresponding to the specified range }
  714. { the caller is responsible for disposing of the returned region }
  715. { when it's finished with it }
  716.  
  717.         var
  718.             pWE: WEPtr;
  719.             hiliteRgn: RgnHandle;
  720.             selRect: LongRect;
  721.             firstPoint, lastPoint: LongPoint;
  722.             firstLineHeight, lastLineHeight: Integer;
  723.             r: Rect;
  724.             savePort: GrafPtr;
  725.             saveWELock: Boolean;
  726.     begin
  727.  
  728. { lock the WE record }
  729.         saveWELock := _WESetHandleLock(hWE, true);
  730.         pWE := hWE^;
  731.  
  732. { set up the port }
  733.         GetPort(savePort);
  734.         SetPort(pWE^.port);
  735.  
  736. { make sure rangeStart comes before rangeEnd }
  737.         _WEReorder(rangeStart, rangeEnd);
  738.  
  739. { calculate pixel location corresponding to rangeStart }
  740.         WEGetPoint(rangeStart, firstPoint, firstLineHeight, hWE);
  741.  
  742. { calculate pixel location corresponding to rangeEnd }
  743.         WEGetPoint(rangeEnd, lastPoint, lastLineHeight, hWE);
  744.  
  745. { open a region: rects to be hilited will be accumulated in this }
  746.         OpenRgn;
  747.  
  748.         if (firstPoint.v = lastPoint.v) then
  749.             begin
  750.  
  751. { selection range encompasses only one line }
  752.                 WESetLongRect(selRect, firstPoint.h, firstPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  753.                 WELongRectToRect(selRect, r);
  754.                 FrameRect(r);
  755.             end
  756.         else
  757.             begin
  758.  
  759. { selection range encompasses more than one line }
  760. { hilite the first line }
  761.                 WESetLongRect(selRect, firstPoint.h, firstPoint.v, pWE^.destRect.right, firstPoint.v + firstLineHeight);
  762.                 WELongRectToRect(selRect, r);
  763.                 FrameRect(r);
  764.  
  765. { any lines between the first and the last one? }
  766.                 if (firstPoint.v + firstLineHeight < lastPoint.v) then
  767.                     begin
  768.  
  769. { hilite all the lines in-between }
  770.                         WESetLongRect(selRect, pWE^.destRect.left, firstPoint.v + firstLineHeight, pWE^.destRect.right, lastPoint.v);
  771.                         WELongRectToRect(selRect, r);
  772.                         FrameRect(r);
  773.                     end;
  774.  
  775. { hilite the last line }
  776.                 WESetLongRect(selRect, pWE^.destRect.left, lastPoint.v, lastPoint.h, lastPoint.v + lastLineHeight);
  777.                 WELongRectToRect(selRect, r);
  778.                 FrameRect(r);
  779.             end;
  780.  
  781. { copy the accumulated region into a new region }
  782.         hiliteRgn := NewRgn;
  783.         CloseRgn(hiliteRgn);
  784.  
  785. { restrict this region to the view region }
  786.         SectRgn(hiliteRgn, pWE^.viewRgn, hiliteRgn);
  787.  
  788. { restore the port }
  789.         SetPort(savePort);
  790.  
  791. { unlock the WE record }
  792.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  793.  
  794. { return the hilite region }
  795.         WEGetHiliteRgn := hiliteRgn;
  796.  
  797.     end;  { WEGetHiliteRgn }
  798.  
  799.     procedure _WEHiliteRange (rangeStart, rangeEnd: LongInt;
  800.                                     hWE: WEHandle);
  801.         var
  802.             pWE: WEPtr;
  803.             saveClip, auxRgn, hiliteRgn: RgnHandle;
  804.             savePen: PenState;
  805.             savePort: GrafPtr;
  806.     begin
  807.  
  808. { the WE record must be already locked }
  809.         pWE := hWE^;
  810.  
  811. { do nothing if the specified range is empty }
  812.         if (rangeStart = rangeEnd) then
  813.             Exit(_WEHiliteRange);
  814.  
  815. { set up the port }
  816.         GetPort(savePort);
  817.         SetPort(pWE^.port);
  818.  
  819. { create auxiliary regions }
  820.         saveClip := NewRgn;
  821.         auxRgn := NewRgn;
  822.  
  823. { restrict the clip region to the view rectangle }
  824.         GetClip(saveClip);
  825.         SectRgn(saveClip, pWE^.viewRgn, auxRgn);
  826.         SetClip(auxRgn);
  827.  
  828. { get the hilite region corresponding to the specified range }
  829.         hiliteRgn := WEGetHiliteRgn(rangeStart, rangeEnd, hWE);
  830.  
  831. { hilite the region or frame it, depending on the setting of the active flag }
  832.         if BTST(pWE^.flags, weFActive) then
  833.             begin
  834.                 ClearHiliteBit;
  835.                 InvertRgn(hiliteRgn);
  836.             end
  837.         else if BTST(pWE^.flags, weFOutlineHilite) then
  838.             begin
  839.                 GetPenState(savePen);
  840.                 PenNormal;
  841.                 PenMode(patXor);
  842.                 ClearHiliteBit;
  843.                 FrameRgn(hiliteRgn);
  844.                 SetPenState(savePen);
  845.             end;
  846.  
  847. { restore the clip region }
  848.         SetClip(saveClip);
  849.  
  850. { dispose of all regions }
  851.         DisposeRgn(saveClip);
  852.         DisposeRgn(auxRgn);
  853.         DisposeRgn(hiliteRgn);
  854.  
  855. { restore the port }
  856.         SetPort(savePort);
  857.  
  858.     end;  { _WEHiliteRange }
  859.  
  860.     procedure WESetSelection (selStart, selEnd: LongInt;
  861.                                     hWE: WEHandle);
  862.         var
  863.             pWE: WEPtr;
  864.             oldSelStart, oldSelEnd: LongInt;
  865.             saveWELock: Boolean;
  866.     begin
  867.  
  868. { lock the WE record }
  869.         saveWELock := _WESetHandleLock(hWE, true);
  870.         pWE := hWE^;
  871.  
  872. { invalid the null style }
  873.         BCLR(pWE^.flags, weFUseNullStyle);
  874.  
  875. { hide the caret if it's showing }
  876.         if BTST(pWE^.flags, weFCaretVisible) then
  877.             _WEBlinkCaret(hWE);
  878.  
  879. { range-check parameters }
  880.         selStart := _WEPinInRange(selStart, 0, pWE^.textLength);
  881.         selEnd := _WEPinInRange(selEnd, 0, pWE^.textLength);
  882.  
  883. { set the weFAnchorIsEnd bit if selStart > selEnd, then reorder the endpoints }
  884.         if (selStart > selEnd) then
  885.             BSET(pWE^.flags, weFAnchorIsEnd)
  886.         else
  887.             BCLR(pWE^.flags, weFAnchorIsEnd);
  888.         _WEReorder(selStart, selEnd);
  889.  
  890. { get old selection range }
  891.         oldSelStart := pWE^.selStart;
  892.         oldSelEnd := pWE^.selEnd;
  893.  
  894. { set new selection range }
  895.         pWE^.selStart := selStart;
  896.         pWE^.selEnd := selEnd;
  897.  
  898. { REDRAW THE SELECTION }
  899. { skip this section if redrawing has been inhibited }
  900.         if (not BTST(pWE^.flags, weFInhibitRecal)) then
  901.             begin
  902.  
  903. { if we're active, invert the exclusive-OR between the old range and the new range. }
  904. { if we're inactive, this optimization can't be used because of outline highlighting. }
  905.                 if BTST(pWE^.flags, weFActive) then
  906.                     begin
  907.                         _WEReorder(oldSelStart, selStart);
  908.                         _WEReorder(oldSelEnd, selEnd);
  909.                         _WEReorder(oldSelEnd, selStart);
  910.                     end;
  911.  
  912.                 _WEHiliteRange(oldSelStart, oldSelEnd, hWE);
  913.                 _WEHiliteRange(selStart, selEnd, hWE);
  914.  
  915.                 if (not BTST(pWE^.flags, weFMouseTracking)) then
  916.                     begin
  917.  
  918. { redraw the caret immediately, if the selection range is empty }
  919.                         if (pWE^.selStart = pWE^.selEnd) then
  920.                             _WEBlinkCaret(hWE);
  921.  
  922. { clear clickCount, unless we're tracking the mouse }
  923.                         pWE^.clickCount := 0;
  924.  
  925. { scroll the selection into view, unless we're tracking the mouse }
  926.                         WESelView(hWE);
  927.  
  928.                     end;
  929.             end;  { if redrawing not inhibited }
  930.  
  931. { unlock the WE record }
  932.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  933.  
  934.     end;  { WESetSelection }
  935.  
  936.     procedure WESetAlignment (alignment: SignedByte;
  937.                                     hWE: WEHandle);
  938.     begin
  939.         if ((alignment >= weFlushLeft) and (alignment <= weJustify)) then
  940.             if (alignment <> hWE^^.alignment) then
  941.                 begin
  942.  
  943. { hide the caret if it's showing }
  944.                     if BTST(hWE^^.flags, weFCaretVisible) then
  945.                         _WEBlinkCaret(hWE);
  946.  
  947. { change the alignment }
  948.                     hWE^^.alignment := alignment;
  949.  
  950. { redraw the view rectangle }
  951.                     WEUpdate(nil, hWE);
  952.                 end;
  953.     end;  { WESetAlignment }
  954.  
  955.     function _WEArrowOffset (action: Integer;
  956.                                     offset: LongInt;
  957.                                     hWE: WEHandle): LongInt;
  958.  
  959. { given an action code (corresponding to a modifiers + arrow key combo) }
  960. { and an offset into the text, find the offset of the new caret position }
  961.  
  962.         var
  963.             thePoint: LongPoint;
  964.             textLength, rangeStart, rangeEnd: LongInt;
  965.             lineHeight: Integer;
  966.             edge: SignedByte;
  967.     begin
  968.         textLength := hWE^^.textLength;
  969.         case action of
  970.  
  971.             kGoLeft: 
  972.                 if (offset > 0) then
  973.                     begin
  974.                         offset := offset - 1;
  975.                         if (WECharByte(offset, hWE) <> smSingleByte) then
  976.                             offset := offset - 1;
  977.                     end;
  978.  
  979.             kGoRight: 
  980.                 if (offset < textLength) then
  981.                     begin
  982.                         if (WECharByte(offset, hWE) <> smSingleByte) then
  983.                             offset := offset + 1;
  984.                         offset := offset + 1;
  985.                     end;
  986.  
  987.             kGoUp: 
  988.                 begin
  989.                     WEGetPoint(offset, thePoint, lineHeight, hWE);
  990.                     thePoint.v := thePoint.v - 1;
  991.                     offset := WEGetOffset(thePoint, edge, hWE);
  992.                 end;
  993.  
  994.             kGoDown: 
  995.                 begin
  996.                     WEGetPoint(offset, thePoint, lineHeight, hWE);
  997.                     thePoint.v := thePoint.v + lineHeight;
  998.                     offset := WEGetOffset(thePoint, edge, hWE);
  999.                 end;
  1000.  
  1001.             kGoWordStart: 
  1002.                 begin
  1003.                     WEFindWord(offset, kTrailingEdge, rangeStart, rangeEnd, hWE);
  1004.                     offset := rangeStart;
  1005.                 end;
  1006.  
  1007.             kGoWordEnd: 
  1008.                 begin
  1009.                     WEFindWord(offset, kLeadingEdge, rangeStart, rangeEnd, hWE);
  1010.                     offset := rangeEnd;
  1011.                 end;
  1012.  
  1013.             kGoTextStart: 
  1014.                 offset := 0;
  1015.  
  1016.             kGoTextEnd: 
  1017.                 offset := textLength;
  1018.  
  1019.             kGoLineStart: 
  1020.                 begin
  1021.                     WEFindLine(offset, kLeadingEdge, rangeStart, rangeEnd, hWE);
  1022.                     offset := rangeStart;
  1023.                 end;
  1024.  
  1025.             kGoLineEnd: 
  1026.                 begin
  1027.                     WEFindLine(offset, kTrailingEdge, rangeStart, rangeEnd, hWE);
  1028.                     offset := rangeEnd;
  1029.                     if (offset < textLength) then
  1030.                         begin
  1031.                             offset := offset - 1;
  1032.                             if (WECharByte(offset, hWE) <> smSingleByte) then
  1033.                                 offset := offset - 1;
  1034.                         end;
  1035.                 end;
  1036.  
  1037.             otherwise
  1038.                 ;
  1039.         end;  { case action }
  1040.  
  1041.         _WEArrowOffset := offset;
  1042.  
  1043.     end;  { _WEArrowOffset }
  1044.  
  1045.     procedure _WEDoArrowKey (arrow: Integer;
  1046.                                     modifiers: Integer;
  1047.                                     hWE: WEHandle);
  1048.  
  1049. { this routine is called by WEKey to handle arrow keys }
  1050. { the WE record is guaranteed to be already locked }
  1051.  
  1052.         var
  1053.             pWE: WEPtr;
  1054.             action: Integer;
  1055.             selStart, selEnd: LongInt;
  1056.             caretLoc, anchor: LongInt;
  1057.     begin
  1058.         pWE := hWE^;
  1059.  
  1060. { calculate the "action" parameter for _WEArrowOffset from arrow and modifiers }
  1061.         action := arrow - kArrowLeft;            { possible range: 0..3 }
  1062.         if (BitAnd(modifiers, optionKey) <> 0) then
  1063.             action := action + kOption;
  1064.         if (BitAnd(modifiers, cmdKey) <> 0) then
  1065.             action := action + kCommand;
  1066.  
  1067. { get selection range }
  1068.         selStart := pWE^.selStart;
  1069.         selEnd := pWE^.selEnd;
  1070.  
  1071.         if (BitAnd(modifiers, shiftKey) = 0) then
  1072.             begin
  1073.  
  1074. { if selection range isn't empty, collapse it to one of the endpoints }
  1075.                 if (selStart < selEnd) then
  1076.                     if ((arrow = kArrowLeft) or (arrow = kArrowUp)) then
  1077.                         caretLoc := selStart
  1078.                     else
  1079.                         caretLoc := selEnd
  1080.                 else
  1081.  
  1082. { otherwise move the insertion point }
  1083.                     caretLoc := _WEArrowOffset(action, selStart, hWE);
  1084.  
  1085. { set anchor to caretLoc, so new selection will be empty }
  1086.                 anchor := caretLoc;
  1087.  
  1088.             end
  1089.         else
  1090.             begin
  1091.  
  1092. { shift key was held down: extend the selection rather than replacing it }
  1093. { find out which selection boundary is the anchor and which is the free endpoint }
  1094.                 if BTST(pWE^.flags, weFAnchorIsEnd) then
  1095.                     begin
  1096.                         anchor := selEnd;
  1097.                         caretLoc := selStart;
  1098.                     end
  1099.                 else
  1100.                     begin
  1101.                         anchor := selStart;
  1102.                         caretLoc := selEnd;
  1103.                     end;
  1104.  
  1105. { move the free endpoint }
  1106.                 caretLoc := _WEArrowOffset(action, caretLoc, hWE);
  1107.  
  1108.             end;
  1109.  
  1110. { select the new selection }
  1111.         WESetSelection(anchor, caretLoc, hWE);
  1112.  
  1113.     end;  { _WEDoArrowKey }
  1114.  
  1115.     function WEAdjustCursor (mouseLoc: Point;
  1116.                                     mouseRgn: RgnHandle;
  1117.                                     hWE: WEHandle): Boolean;
  1118.  
  1119. { Call WEAdjustCursor to set the cursor shape when the mouse is in the view rectangle. }
  1120. { MouseRgn should be either a valid region handle or NIL. }
  1121. { If mouseRgn is supplied (i.e., if it's not NIL), it is intersected with a region }
  1122. { in global coordinates within which the cursor is to retain its shape. }
  1123. { WEAdjustCursor returns TRUE if the cursor has been set. }
  1124. { Your application should set the cursor only if WEAdjustCursor returns FALSE. }
  1125.  
  1126.         var
  1127.             pWE: WEPtr;
  1128.             auxRgn, hiliteRgn: RgnHandle;
  1129.             cursorType: (kIBeam, kArrow);
  1130.             portDelta: Point;
  1131.             savePort: GrafPtr;
  1132.             saveWELock: Boolean;
  1133.     begin
  1134.         WEAdjustCursor := false;
  1135.         cursorType := kIBeam;
  1136.  
  1137. { lock the WE record }
  1138.         saveWELock := _WESetHandleLock(hWE, true);
  1139.         pWE := hWE^;
  1140.  
  1141. { set up the port }
  1142.         GetPort(savePort);
  1143.         SetPort(pWE^.port);
  1144.  
  1145. { calculate delta between the local coordinate system and the global one }
  1146.         portDelta := Point(0);
  1147.         LocalToGlobal(portDelta);
  1148.  
  1149. { calculate the visible portion of the view rectangle, in global coordinates }
  1150.         auxRgn := NewRgn;
  1151.         CopyRgn(pWE^.viewRgn, auxRgn);
  1152.         SectRgn(auxRgn, pWE^.port^.visRgn, auxRgn);
  1153.         OffsetRgn(auxRgn, portDelta.h, portDelta.v);
  1154.  
  1155.         if PtInRgn(mouseLoc, auxRgn) then
  1156.             begin
  1157.  
  1158. { mouse is within view rectangle: it's up to us to set the cursor }
  1159.                 WEAdjustCursor := true;
  1160.  
  1161. { if drag-and-drop is enabled, see if the mouse is within current selection }
  1162.                 if BTST(pWE^.flags, weFDragAndDrop) then
  1163.                     if (pWE^.selStart < pWE^.selEnd) then
  1164.                         begin
  1165.  
  1166. { get current hilite region in global coordinates }
  1167.                             hiliteRgn := WEGetHiliteRgn(pWE^.selStart, pWE^.selEnd, hWE);
  1168.                             OffsetRgn(hiliteRgn, portDelta.h, portDelta.v);
  1169.  
  1170. { if mouse is within selection, set cursor to an arrow, else to an I-beam }
  1171. { (actually, we still use an I-beam if less than DoubleTime ticks have elapsed }
  1172. { since the last mouse click, so that the cursor doesn't turn into an arrow while }
  1173. { triple-clicking + dragging a range of lines) }
  1174.  
  1175.                             if (PtInRgn(mouseLoc, hiliteRgn) and (TickCount > pWE^.clickTime + GetDblTime)) then
  1176.                                 begin
  1177.                                     cursorType := kArrow;                { use arrow cursor }
  1178.                                     CopyRgn(hiliteRgn, auxRgn);
  1179.                                 end
  1180.                             else
  1181.                                 DiffRgn(auxRgn, hiliteRgn, auxRgn);
  1182.  
  1183. { dispose of the hilite region }
  1184.                             DisposeRgn(hiliteRgn);
  1185.  
  1186.                         end;  { if drag-and-drop is enabled }
  1187.  
  1188. { set the cursor }
  1189.                 if (cursorType = kIBeam) then
  1190.                     SetCursor(GetCursor(iBeamCursor)^^)
  1191.                 else
  1192.                     SetCursor(GetQDGlobals^.arrow);
  1193.  
  1194. { set mouseRgn, if provided }
  1195.                 if (mouseRgn <> nil) then
  1196.                     SectRgn(mouseRgn, auxRgn, mouseRgn);
  1197.  
  1198.             end
  1199.         else
  1200.  
  1201. { mouse is outside view rectangle: don't set the cursor; subtract viewRgn from mouseRgn }
  1202.             if (mouseRgn <> nil) then
  1203.                 DiffRgn(mouseRgn, auxRgn, mouseRgn);
  1204.  
  1205. { dispose of the temporary region }
  1206.         DisposeRgn(auxRgn);
  1207.  
  1208. { restore the port }
  1209.         SetPort(savePort);
  1210.  
  1211. { unlock the WE record }
  1212.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1213.  
  1214.     end;  { WEAdjustCursor }
  1215.  
  1216.     procedure WEIdle (var maxSleep: LongInt;
  1217.                                     hWE: WEHandle);
  1218.         var
  1219.             pWE: WEPtr;
  1220.             caretInterval, sleepTime: LongInt;
  1221.             saveWELock: Boolean;
  1222.     begin
  1223.  
  1224. { lock the WE record }
  1225.         saveWELock := _WESetHandleLock(hWE, true);
  1226.         pWE := hWE^;
  1227.  
  1228. { the caret blinks only if we're active and the selection point is empty }
  1229.         if (BTST(pWE^.flags, weFActive) and (pWE^.selStart = pWE^.selEnd)) then
  1230.             begin
  1231.  
  1232. { the low-memory global variable CaretTime contains the preferred interval }
  1233. { between successive inversions of the caret }
  1234.                 caretInterval := GetCaretTime;
  1235.  
  1236. { calculate how many ticks we can sleep before we need to invert the caret }
  1237. { the caretTime field of the WE record contains the time of the last inversion }
  1238.                 sleepTime := caretInterval - (TickCount - pWE^.caretTime);
  1239.  
  1240. { if sleepTime has gone negative, invert the caret }
  1241.                 if (sleepTime <= 0) then
  1242.                     begin
  1243.                         _WEBlinkCaret(hWE);
  1244.                         sleepTime := caretInterval;
  1245.                     end;
  1246.  
  1247.             end
  1248.         else
  1249.  
  1250. { if we don't need to blink the caret, we can sleep forever }
  1251.             sleepTime := maxLongInt;
  1252.  
  1253. { return sleepTime to the caller if maxSleep isn't NIL }
  1254.         if (@maxSleep <> nil) then
  1255.             maxSleep := sleepTime;
  1256.  
  1257. { unlock the WE record }
  1258.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1259.  
  1260.     end;  { WEIdle }
  1261.  
  1262.     procedure WEUpdate (updateRgn: RgnHandle;
  1263.                                     hWE: WEHandle);
  1264.         var
  1265.             pWE: WEPtr;
  1266.             firstLine, lastLine: LongInt;
  1267.             auxRect: Rect;
  1268.             saveClip, auxRgn: RgnHandle;
  1269.             savePort: GrafPtr;
  1270.             saveWELock: Boolean;
  1271.     begin
  1272.  
  1273. { lock the WE record }
  1274.         saveWELock := _WESetHandleLock(hWE, true);
  1275.         pWE := hWE^;
  1276.  
  1277. { set up the port }
  1278.         GetPort(savePort);
  1279.         SetPort(pWE^.port);
  1280.  
  1281. { save the clip region }
  1282.         saveClip := NewRgn;
  1283.         GetClip(saveClip);
  1284.  
  1285. { clip to the insersection between updateRgn and the view rectangle }
  1286. { (updateRgn may be NIL; in this case, just clip to the view rectangle) }
  1287.         auxRgn := NewRgn;
  1288.         if (updateRgn <> nil) then
  1289.             SectRgn(updateRgn, pWE^.viewRgn, auxRgn)
  1290.         else
  1291.             CopyRgn(pWE^.viewRgn, auxRgn);
  1292.         SetClip(auxRgn);
  1293.  
  1294.         if (EmptyRgn(auxRgn) = false) then
  1295.             begin
  1296.  
  1297. { set auxRect to the bounding box of the update region (clipped to the view rectangle) }
  1298.                 auxRect := auxRgn^^.rgnBBox;
  1299.  
  1300. { find out which lines need to be redrawn }
  1301.                 firstLine := _WEPixelToLine(auxRect.top - pWE^.destRect.top, hWE);
  1302.                 lastLine := _WEPixelToLine((auxRect.bottom - 1) - pWE^.destRect.top, hWE);
  1303.  
  1304. { draw them (if updateRgn is NIL, erase each line rectangle before redrawing) }
  1305.                 _WEDrawLines(firstLine, lastLine, (updateRgn = nil), hWE);
  1306.  
  1307. { hilite the selection range or draw the caret (only if active) }
  1308.                 if (pWE^.selStart < pWE^.selEnd) then
  1309.                     _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE)
  1310.                 else if BTST(pWE^.flags, weFCaretVisible) then
  1311.                     begin
  1312.                         _WEBlinkCaret(hWE);
  1313.                         BSET(pWE^.flags, weFCaretVisible);
  1314.                     end;
  1315.  
  1316.             end;  { if not empty }
  1317.  
  1318.         DisposeRgn(auxRgn);
  1319.  
  1320. { restore the clip region }
  1321.         SetClip(saveClip);
  1322.         DisposeRgn(saveClip);
  1323.  
  1324. { restore the port }
  1325.         SetPort(savePort);
  1326.  
  1327. { unlock the WE record }
  1328.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1329.  
  1330.     end;  { WEUpdate }
  1331.  
  1332.     procedure WEDeactivate (hWE: WEHandle);
  1333.         var
  1334.             pWE: WEPtr;
  1335.             saveWELock: Boolean;
  1336.     begin
  1337.  
  1338. { lock the WE record }
  1339.         saveWELock := _WESetHandleLock(hWE, true);
  1340.         pWE := hWE^;
  1341.  
  1342. { do nothing if we are already inactive }
  1343.         if (BTST(pWE^.flags, weFActive)) then
  1344.             begin
  1345.  
  1346. { hide the selection range or the caret }
  1347.                 _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE);
  1348.                 if BTST(pWE^.flags, weFCaretVisible) then
  1349.                     _WEBlinkCaret(hWE);
  1350.  
  1351. { clear the active flag }
  1352.                 BCLR(pWE^.flags, weFActive);
  1353.  
  1354. { frame the selection }
  1355.                 _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE);
  1356.  
  1357. { dispose of the offscreen graphics world, if any }
  1358.                 if (pWE^.offscreenPort <> nil) then
  1359.                     begin
  1360.                         DisposeGWorld(GWorldPtr(pWE^.offscreenPort));
  1361.                         pWE^.offscreenPort := nil;
  1362.                     end;
  1363.  
  1364. { notify Text Services }
  1365.                 if (pWE^.tsmReference <> nil) then
  1366.                     IgnoreShort(DeactivateTSMDocument(pWE^.tsmReference));
  1367.  
  1368.             end;
  1369.  
  1370. { unlock the WE record }
  1371.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1372.  
  1373.     end;  { WEDeactivate }
  1374.  
  1375.     procedure WEActivate (hWE: WEHandle);
  1376.         var
  1377.             pWE: WEPtr;
  1378.             saveWELock: Boolean;
  1379.     begin
  1380.  
  1381. { lock the WE record }
  1382.         saveWELock := _WESetHandleLock(hWE, true);
  1383.         pWE := hWE^;
  1384.  
  1385. { do nothing if we are already active }
  1386.         if (not BTST(pWE^.flags, weFActive)) then
  1387.             begin
  1388.  
  1389. { remove the selection frame }
  1390.                 _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE);
  1391.  
  1392. { set the active flag }
  1393.                 BSET(pWE^.flags, weFActive);
  1394.  
  1395. { show the selection range }
  1396.                 _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE);
  1397.  
  1398. { notify Text Services }
  1399.                 if (pWE^.tsmReference <> nil) then
  1400.                     IgnoreShort(ActivateTSMDocument(pWE^.tsmReference));
  1401.  
  1402.             end;
  1403.  
  1404. { unlock the WE record }
  1405.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1406.  
  1407.     end;  { WEActivate }
  1408.  
  1409.     function WEIsActive (hWE: WEHandle): Boolean;
  1410.     begin
  1411.  
  1412. { return TRUE iff the specified WE instance is currently active }
  1413.         WEIsActive := BTST(hWE^^.flags, weFActive);
  1414.  
  1415.     end;  { WEIsActive }
  1416.  
  1417.     procedure CallScrollProc (hWE: WEHandle;
  1418.                                     scrollProc: ProcPtr);
  1419.     inline
  1420.         $205F,                    { movea.l (sp)+, a0 }
  1421.         $4E90;                    { jsr (a0) }
  1422.  
  1423.     procedure WEScroll (hOffset, vOffset: LongInt;
  1424.                                     hWE: WEHandle);
  1425.         var
  1426.             pWE: WEPtr;
  1427.             viewRect: Rect;
  1428.             updateRgn: RgnHandle;
  1429.             savePort: GrafPtr;
  1430.             hideOutline, saveWELock: Boolean;
  1431.     begin
  1432.  
  1433. { do nothing if both scroll offsets are zero }
  1434.         if ((hOffset = 0) and (vOffset = 0)) then
  1435.             Exit(WEScroll);
  1436.  
  1437. { lock the WE record }
  1438.         saveWELock := _WESetHandleLock(hWE, true);
  1439.         pWE := hWE^;
  1440.  
  1441. { hide the caret if it's showing }
  1442.         if BTST(pWE^.flags, weFCaretVisible) then
  1443.             _WEBlinkCaret(hWE);
  1444.  
  1445. { set up the port }
  1446.         GetPort(savePort);
  1447.         SetPort(pWE^.port);
  1448.  
  1449. { if we're inactive and outline highlighting is on, we have to temporarily }
  1450. { hide the selection outline while scrolling to avoid a cosmetic bug }
  1451.         hideOutline := false;
  1452.         if (not BTST(pWE^.flags, weFActive)) then
  1453.             if (BTST(pWE^.flags, weFOutlineHilite)) then
  1454.                 begin
  1455.                     hideOutline := true;
  1456.                     _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE);
  1457.                     BCLR(pWE^.flags, weFOutlineHilite);
  1458.                 end;
  1459.  
  1460. { if we're currently tracking a drag, notify the Drag Manager we're about to scroll }
  1461.         if (pWE^.currentDrag <> kNullDrag) then
  1462.             IgnoreShort(DragPreScroll(pWE^.currentDrag, hOffset, vOffset));
  1463.  
  1464.         viewRect := pWE^.viewRgn^^.rgnBBox;
  1465.         updateRgn := NewRgn;
  1466.  
  1467. { offset the destination rectangle by the specified amount }
  1468.         WEOffsetLongRect(pWE^.destRect, hOffset, vOffset);
  1469.  
  1470. { scroll the view rectangle }
  1471.         ScrollRect(viewRect, hOffset, vOffset, updateRgn);
  1472.  
  1473. { redraw the exposed region }
  1474.         WEUpdate(updateRgn, hWE);
  1475.         DisposeRgn(updateRgn);
  1476.  
  1477. { notify the Drag Manager }
  1478.         if (pWE^.currentDrag <> kNullDrag) then
  1479.             IgnoreShort(DragPostScroll(pWE^.currentDrag));
  1480.  
  1481. { redraw the selection outline, if hidden }
  1482.         if (hideOutline) then
  1483.             begin
  1484.                 BSET(pWE^.flags, weFOutlineHilite);
  1485.                 _WEHiliteRange(pWE^.selStart, pWE^.selEnd, hWE);
  1486.             end;
  1487.  
  1488. { restore the port }
  1489.         SetPort(savePort);
  1490.  
  1491. { unlock the WE record }
  1492.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1493.  
  1494.     end;  { WEScroll }
  1495.  
  1496.     function _WEScrollIntoView (offset: LongInt;
  1497.                                     hWE: WEHandle): Boolean;
  1498.         var
  1499.             pWE: WEPtr;
  1500.             thePoint: LongPoint;
  1501.             lineHeight: Integer;
  1502.             hScroll, vScroll, temp: LongInt;
  1503.     begin
  1504.         pWE := hWE^;
  1505.  
  1506. { do nothing if automatic scrolling is disabled }
  1507.         if (not BTST(pWE^.flags, weFAutoScroll)) then
  1508.             Exit(_WEScrollIntoView);
  1509.  
  1510. { find the selection point }
  1511.         WEGetPoint(offset, thePoint, lineHeight, hWE);
  1512.  
  1513. { assume no scrolling is needed }
  1514.         _WEScrollIntoView := false;
  1515.         vScroll := 0;
  1516.         hScroll := 0;
  1517.  
  1518. { determine if we need to scroll vertically }
  1519.         if (thePoint.v < pWE^.viewRect.top) or (thePoint.v + lineHeight >= pWE^.viewRect.bottom) then
  1520.             begin
  1521.  
  1522. { calculate the amount of vertical scrolling needed to center the selection into view }
  1523.                 vScroll := BSR(pWE^.viewRect.top + pWE^.viewRect.bottom, 1) - thePoint.v;
  1524.  
  1525. { we'd like to superimpose the bottom margins of the dest/view rects, if possible }
  1526.                 temp := pWE^.viewRect.bottom - pWE^.destRect.bottom;
  1527.                 if (temp > vScroll) then
  1528.                     vScroll := temp;
  1529.  
  1530. { but we also have to make sure the dest top isn't scrolled below the view top }
  1531.                 temp := pWE^.viewRect.top - pWE^.destRect.top;
  1532.                 if (temp < vScroll) then
  1533.                     vScroll := temp;
  1534.  
  1535.             end;
  1536.  
  1537. { determine if we need to scroll horizontally }
  1538.         if (thePoint.h - 1 < pWE^.viewRect.left) or (thePoint.h >= pWE^.viewRect.right) then
  1539.             begin
  1540.  
  1541. { calculate the amount of horizontal scrolling needed to center the selection into view }
  1542.                 hScroll := BSR(pWE^.viewRect.left + pWE^.viewRect.right, 1) - thePoint.h;
  1543.  
  1544. { we'd like to superimpose the right margins of the dest/view rects, if possible }
  1545.                 temp := pWE^.viewRect.right - pWE^.destRect.right;
  1546.                 if (temp > hScroll) then
  1547.                     hScroll := temp;
  1548.  
  1549. { but we also have to make sure the dest left isn't scrolled to the right of the view left }
  1550.                 temp := pWE^.viewRect.left - pWE^.destRect.left;
  1551.                 if (temp < hScroll) then
  1552.                     hScroll := temp;
  1553.  
  1554.             end;
  1555.  
  1556. { scroll the text if necessary }
  1557.         if ((vScroll <> 0) or (hScroll <> 0)) then
  1558.             begin
  1559.                 _WEScrollIntoView := true;
  1560.                 WEScroll(hScroll, vScroll, hWE);
  1561.             end;
  1562.  
  1563. { call the scroll callback, if any }
  1564.         if (pWE^.scrollProc <> nil) then
  1565.             CallScrollProc(hWE, pWE^.scrollProc);
  1566.  
  1567.     end;  { _WEScrollIntoView }
  1568.  
  1569.     procedure WESelView (hWE: WEHandle);
  1570.         var
  1571.             pWE: WEPtr;
  1572.             offset: LongInt;
  1573.             saveWELock: Boolean;
  1574.     begin
  1575.  
  1576. { lock the WE record }
  1577.         saveWELock := _WESetHandleLock(hWE, true);
  1578.         pWE := hWE^;
  1579.  
  1580. { scroll the free endpoint of the selection into view }
  1581.         if (BTST(pWE^.flags, weFAnchorIsEnd)) then
  1582.             offset := pWE^.selStart
  1583.         else
  1584.             offset := pWE^.selEnd;
  1585.         IgnoreBoolean(_WEScrollIntoView(offset, hWE));
  1586.  
  1587. { unlock the WE record }
  1588.         IgnoreBoolean(_WESetHandleLock(hWE, saveWELock));
  1589.  
  1590.     end;  { WESelView }
  1591.  
  1592. end.